#include <assert.h>
#include <fstream>
#include <iostream>
#include "Mesh3D.hpp"
#include "IndexBuffer.hpp"
#include "../Extensions/ExtensionsLoader.hpp"

using namespace std;

Mesh3D::Mesh3D()
{
	m_pIdxBuffer = new IndexBuffer();
	m_fAngleZ = 0.0f;
}

Mesh3D::Mesh3D(std::string fileName)
{
	m_pIdxBuffer = new IndexBuffer();
	loadGMF(fileName);
	m_fAngleZ = 0.0f;
}

Mesh3D::Mesh3D(char* pDataBuffer, int iBufferSize)
{
	m_pIdxBuffer = new IndexBuffer();
	loadGMF(pDataBuffer, iBufferSize);
	m_fAngleZ = 0.0f;
}

Mesh3D::~Mesh3D()
{
	delete m_pIdxBuffer; m_pIdxBuffer = NULL;
	ExtensionsLoader::getInstance()->glDeleteBuffersARB(BUFFER_TYPES_NUM, m_bufferNames);
}

void Mesh3D::render()
{
	//glPushMatrix();

	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[POSITION_OBJECT]);
	glVertexPointer(3, GL_FLOAT, 0, 0);

	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[NORMAL_OBJECT]);
	glNormalPointer(GL_FLOAT, 0, 0);

	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[UV_OBJECT]);
	glTexCoordPointer(2, GL_FLOAT, 0, 0);

	//! Unbind buffers.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, 0);

	
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glRotatef(m_fAngleZ, 0.0, 0.0, 1.0);

	glDrawArrays(GL_TRIANGLES, 0, m_iDrawVertices);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	//glPopMatrix();
}

void Mesh3D::getCollisionObj()
{
}

bool Mesh3D::loadGMF(std::string fileName)
{
	ifstream modelFile;
	int iFileSize, iNumVertices, iNumNormals, iNumTexCoords, iNumFaces;
	char* pFileContent;
	char* offset;

	modelFile.open(fileName.c_str(), ios::in | ios::binary);
	assert(modelFile.is_open());

	modelFile.seekg(0, ios::end);
	iFileSize = modelFile.tellg();
	pFileContent = new char[iFileSize];
	modelFile.seekg(0, ios::beg);

	modelFile.read(pFileContent, iFileSize);
	modelFile.close();

	offset = pFileContent;

	//! Check if valid GMF file.
	if(pFileContent[0] != 'G' || pFileContent[1] != 'M' || pFileContent[2] != 'F')
		assert(false);
	else
		offset += 3;

	//! Read number of vertices, normals, tex coords and faces.
	memcpy(&iNumVertices, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumNormals, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumTexCoords, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumFaces, offset, sizeof(int));
	offset += sizeof(int);

	//! Count how many indexes are stored per vertex.
	int coordsPerVertex = 0;
	if(iNumVertices > 0)
		++coordsPerVertex;

	if(iNumNormals > 0)
		++coordsPerVertex;

	if(iNumTexCoords > 0)
		++coordsPerVertex;


	m_pIdxBuffer->m_iBufferSize = iNumFaces * 3 *coordsPerVertex;
	m_pIdxBuffer->m_pIndices = new int[iNumFaces * 3 *coordsPerVertex];

	//! Read vertices.
	m_pVerticesBuffer = new IndexedBuffer<float>();
	m_pVerticesBuffer->m_pBufferElements = new float[iNumVertices *3];
	m_pVerticesBuffer->m_iSize = iNumVertices * 3;

	memcpy(m_pVerticesBuffer->m_pBufferElements, offset, sizeof(float) * iNumVertices * 3);
	offset += sizeof(float) * iNumVertices * 3;


	//! Read normals.
	m_pNormalsBuffer = new IndexedBuffer<float>();
	m_pNormalsBuffer->m_pBufferElements = new float[iNumNormals * 3];
	m_pNormalsBuffer->m_iSize = iNumNormals * 3;

	memcpy(m_pNormalsBuffer->m_pBufferElements, offset, sizeof(float) * iNumNormals * 3);
	offset += sizeof(float) * iNumNormals * 3;


	//! Read tex coords.
	if(iNumTexCoords > 0)
	{
		m_pTexCoordsBuffer = new IndexedBuffer<float>();
		m_pTexCoordsBuffer->m_pBufferElements = new float[iNumTexCoords *2];
		m_pTexCoordsBuffer->m_iSize = iNumTexCoords * 2;
		
		memcpy(m_pTexCoordsBuffer->m_pBufferElements, offset, sizeof(float) * iNumTexCoords * 2);
		offset += sizeof(float) * iNumTexCoords * 2;
	}


	memcpy(m_pIdxBuffer->m_pIndices, offset, sizeof(int) * iNumFaces * 3 * coordsPerVertex);

	GLfloat *vertData, *normData, *uvData;
	int iBuffSize = 0;
	int iUVBuffSize = 0;

	iBuffSize = iNumFaces * 3 * 3;
	iUVBuffSize = iNumFaces * 3 * 2;
	m_iDrawVertices = iNumFaces * 3;

	vertData = new GLfloat[iBuffSize];
	normData = new GLfloat[iBuffSize];
	uvData   = new GLfloat[iUVBuffSize];

	int buffIdx = 0;
	int cnt = 0;

	int fillEnd = m_pIdxBuffer->m_iBufferSize - coordsPerVertex;

	for(int i = 0; i <= fillEnd; i += coordsPerVertex)
	{
		int faceOffset = m_pIdxBuffer->m_pIndices[i]-1;
		int firstCoord = faceOffset*3;

		vertData[buffIdx]   = m_pVerticesBuffer->m_pBufferElements[firstCoord];
		vertData[buffIdx+1] = m_pVerticesBuffer->m_pBufferElements[firstCoord+1];
		vertData[buffIdx+2] = m_pVerticesBuffer->m_pBufferElements[firstCoord+2];

		buffIdx += 3;
		cnt += 1;
	}
	
	buffIdx = 0;

	if(iNumNormals > 0)
	{
		for(int i = 0; i <= fillEnd; i += coordsPerVertex)
		{
			int faceOffset = m_pIdxBuffer->m_pIndices[i+coordsPerVertex-1]-1;
			int firstCoord = faceOffset*3;

			normData[buffIdx]   = m_pNormalsBuffer->m_pBufferElements[firstCoord];
			normData[buffIdx+1] = m_pNormalsBuffer->m_pBufferElements[firstCoord+1];
			normData[buffIdx+2] = m_pNormalsBuffer->m_pBufferElements[firstCoord+2];
		
			buffIdx += 3;
		}
	}

	buffIdx = 0;

	if(iNumTexCoords > 0)
	{
		for(int i = 0; i <= fillEnd; i += coordsPerVertex)
		{
			int faceOffset = m_pIdxBuffer->m_pIndices[i+1]-1;
			int firstCoord = faceOffset*2;

			uvData[buffIdx]   = m_pTexCoordsBuffer->m_pBufferElements[firstCoord]*2.0;
			uvData[buffIdx+1] = m_pTexCoordsBuffer->m_pBufferElements[firstCoord+1]*2.0;
			
			buffIdx += 2;
		}
	}


	//! Create VBOs
	ExtensionsLoader::getInstance()->glGenBuffersARB(BUFFER_TYPES_NUM, m_bufferNames);

	//! Create vertex buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[POSITION_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iBuffSize*sizeof(GLfloat), vertData, GL_STATIC_DRAW);
	glVertexPointer(3, GL_FLOAT, 0, 0);

	//!Create normal buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[NORMAL_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iBuffSize*sizeof(GLfloat), normData, GL_STATIC_DRAW);
	glNormalPointer(GL_FLOAT, 0, 0);

	//!Create uv's coords buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[UV_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iUVBuffSize*sizeof(GLfloat), uvData, GL_STATIC_DRAW);
	glTexCoordPointer(2, GL_FLOAT, 0, 0);

	//! Unbind buffers.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, 0);

	delete[] vertData;
	delete[] normData;
	delete[] uvData;

	return true;
}

//TODO: reduce amount of code

bool Mesh3D::loadGMF(char *pDataBuffer, int iBufferSize)
{
	int iFileSize, iNumVertices, iNumNormals, iNumTexCoords, iNumFaces;
	char* pFileContent;
	char* offset;

	iFileSize = iBufferSize;
	pFileContent = pDataBuffer;

	offset = pFileContent;

	//! Check if valid GMF file.
	if(pFileContent[0] != 'G' || pFileContent[1] != 'M' || pFileContent[2] != 'F')
		assert(false);
	else
		offset += 3;

	//! Read number of vertices, normals, tex coords and faces.
	memcpy(&iNumVertices, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumNormals, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumTexCoords, offset, sizeof(int));
	offset += sizeof(int);

	memcpy(&iNumFaces, offset, sizeof(int));
	offset += sizeof(int);

	//! Count how many indexes are stored per vertex.
	int coordsPerVertex = 0;
	if(iNumVertices > 0)
		++coordsPerVertex;

	if(iNumNormals > 0)
		++coordsPerVertex;

	if(iNumTexCoords > 0)
		++coordsPerVertex;


	m_pIdxBuffer->m_iBufferSize = iNumFaces * 3 *coordsPerVertex;
	m_pIdxBuffer->m_pIndices = new int[iNumFaces * 3 *coordsPerVertex];

	//! Read vertices.
	m_pVerticesBuffer = new IndexedBuffer<float>();
	m_pVerticesBuffer->m_pBufferElements = new float[iNumVertices *3];
	m_pVerticesBuffer->m_iSize = iNumVertices * 3;

	memcpy(m_pVerticesBuffer->m_pBufferElements, offset, sizeof(float) * iNumVertices * 3);
	offset += sizeof(float) * iNumVertices * 3;


	//! Read normals.
	m_pNormalsBuffer = new IndexedBuffer<float>();
	m_pNormalsBuffer->m_pBufferElements = new float[iNumNormals * 3];
	m_pNormalsBuffer->m_iSize = iNumNormals * 3;

	memcpy(m_pNormalsBuffer->m_pBufferElements, offset, sizeof(float) * iNumNormals * 3);
	offset += sizeof(float) * iNumNormals * 3;


	//! Read tex coords.
	if(iNumTexCoords > 0)
	{
		m_pTexCoordsBuffer = new IndexedBuffer<float>();
		m_pTexCoordsBuffer->m_pBufferElements = new float[iNumTexCoords *2];
		m_pTexCoordsBuffer->m_iSize = iNumTexCoords * 2;
		
		memcpy(m_pTexCoordsBuffer->m_pBufferElements, offset, sizeof(float) * iNumTexCoords * 2);
		offset += sizeof(float) * iNumTexCoords * 2;
	}


	memcpy(m_pIdxBuffer->m_pIndices, offset, sizeof(int) * iNumFaces * 3 * coordsPerVertex);

	GLfloat *vertData, *normData, *uvData;
	int iBuffSize = 0;
	int iUVBuffSize = 0;

	iBuffSize = iNumFaces * 3 * 3;
	iUVBuffSize = iNumFaces * 3 * 2;
	m_iDrawVertices = iNumFaces * 3;

	vertData = new GLfloat[iBuffSize];
	normData = new GLfloat[iBuffSize];
	uvData   = new GLfloat[iUVBuffSize];

	int buffIdx = 0;
	int cnt = 0;

	int fillEnd = m_pIdxBuffer->m_iBufferSize - coordsPerVertex;

	for(int i = 0; i <= fillEnd; i += coordsPerVertex)
	{
		int faceOffset = m_pIdxBuffer->m_pIndices[i]-1;
		int firstCoord = faceOffset*3;

		vertData[buffIdx]   = m_pVerticesBuffer->m_pBufferElements[firstCoord];
		vertData[buffIdx+1] = m_pVerticesBuffer->m_pBufferElements[firstCoord+1];
		vertData[buffIdx+2] = m_pVerticesBuffer->m_pBufferElements[firstCoord+2];

		buffIdx += 3;
		cnt += 1;
	}
	
	buffIdx = 0;

	if(iNumNormals > 0)
	{
		for(int i = 0; i <= fillEnd; i += coordsPerVertex)
		{
			int faceOffset = m_pIdxBuffer->m_pIndices[i+coordsPerVertex-1]-1;
			int firstCoord = faceOffset*3;

			normData[buffIdx]   = m_pNormalsBuffer->m_pBufferElements[firstCoord];
			normData[buffIdx+1] = m_pNormalsBuffer->m_pBufferElements[firstCoord+1];
			normData[buffIdx+2] = m_pNormalsBuffer->m_pBufferElements[firstCoord+2];
		
			buffIdx += 3;
		}
	}

	buffIdx = 0;

	if(iNumTexCoords > 0)
	{
		for(int i = 0; i <= fillEnd; i += coordsPerVertex)
		{
			int faceOffset = m_pIdxBuffer->m_pIndices[i+1]-1;
			int firstCoord = faceOffset*2;

			uvData[buffIdx]   = m_pTexCoordsBuffer->m_pBufferElements[firstCoord];
			uvData[buffIdx+1] = m_pTexCoordsBuffer->m_pBufferElements[firstCoord+1];
			
			buffIdx += 2;
		}
	}


	//! Create VBOs
	ExtensionsLoader::getInstance()->glGenBuffersARB(BUFFER_TYPES_NUM, m_bufferNames);

	//! Create vertex buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[POSITION_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iBuffSize*sizeof(GLfloat), vertData, GL_STATIC_DRAW);
	glVertexPointer(3, GL_FLOAT, 0, 0);

	//!Create normal buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[NORMAL_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iBuffSize*sizeof(GLfloat), normData, GL_STATIC_DRAW);
	glNormalPointer(GL_FLOAT, 0, 0);

	//!Create uv's coords buffer.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, m_bufferNames[UV_OBJECT]);
	ExtensionsLoader::getInstance()->glBufferDataARB(GL_ARRAY_BUFFER, iUVBuffSize*sizeof(GLfloat), uvData, GL_STATIC_DRAW);
	glTexCoordPointer(2, GL_FLOAT, 0, 0);

	//! Unbind buffers.
	ExtensionsLoader::getInstance()->glBindBufferARB(GL_ARRAY_BUFFER, 0);

	delete[] vertData;
	delete[] normData;
	delete[] uvData;

	delete[] pDataBuffer;

	return true;
}

float Mesh3D::getMaximum(COORDS_AXIS axis)
{
	float max = 0.0f;
	int start;

	if(axis == X_AXIS)
		start = 0;
	else if(axis == Y_AXIS)
		start = 1;
	else
		start = 2;

	if(!m_pVerticesBuffer)
		return max;
	else
	{
		for(int i = start; i < m_pVerticesBuffer->m_iSize - 3; i += 3)
		{
			if(m_pVerticesBuffer->m_pBufferElements[i] > max)
				max = m_pVerticesBuffer->m_pBufferElements[i];
		}
	}

	return max;
}

float Mesh3D::getMinimum(COORDS_AXIS axis)
{
	float min = 0xFFFFFFFF;
	int start;

	if(axis == X_AXIS)
		start = 0;
	else if(axis == Y_AXIS)
		start = 1;
	else
		start = 2;

	if(!m_pVerticesBuffer)
		return min;
	else
	{
		for(int i = start; i < m_pVerticesBuffer->m_iSize - 3; i += 3)
		{
			if(m_pVerticesBuffer->m_pBufferElements[i] < min)
				min = m_pVerticesBuffer->m_pBufferElements[i];
		}
	}

	return min;
}

void Mesh3D::rotateZ(float fRot)
{
	m_fAngleZ += fRot;
}

float& Mesh3D::getRotationZ()
{
	return m_fAngleZ;
}

//EOF
